﻿using Model;
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using ME = Model.Environment;

namespace Interpreter
{
	public class StringInterpreter
	{
		private Dictionary<string, List<Expression>> expressions;
		public Dictionary<string, List<Expression>> Expressions
		{
			get { return expressions; }
			set { expressions = value; }
		}

		public StringInterpreter()
		{
			expressions = new Dictionary<string, List<Expression>>();
		}

		public void EvaluateRules(string[] rules)
		{
			var expressions = this.expressions;
			this.expressions = new Dictionary<string, List<Expression>>();
			try
			{
				foreach (string tmpString in rules)
				{
					if (tmpString != null && tmpString.Length > 0 && !tmpString.StartsWith("#"))
					{
						evaluateRuleExpression(tmpString);
					}
				}
			}
			catch (Exception ex)
			{
				this.expressions = expressions;
				throw ex;
			}
		}

		public Expression evaluateRuleExpression(string expression)
		{
			string[] expressionCauseSplit = expression.Split(new string[] { "=>" }, StringSplitOptions.None);
			string expressionWithoutCause = expressionCauseSplit[0];
			string cause = expressionCauseSplit[1];
			if (expressionWithoutCause.EndsWith(" "))
			{
				expressionWithoutCause = expressionWithoutCause.Substring(0, expressionWithoutCause.Length - 1);
			}
			int numberKlammerAuf = 0;
			int numberKlammerZu = 0;

			for (int i = 0; i < expressionWithoutCause.Length; i++)
			{
				if (expressionWithoutCause[i] == '(')
					numberKlammerAuf++;
				if (expressionWithoutCause[i] == ')')
					numberKlammerZu++;
			}

			if (numberKlammerAuf != numberKlammerZu)
				throw new Exception("Ungültige Expression: " + numberKlammerAuf
						+ " mal (, " + numberKlammerZu + " mal ).");

			String[] nameSeperiert = expressionWithoutCause.Split('(');
			string name = nameSeperiert[0];

			if (ME.CellDefinitions[name] != null)
			{
				CellDefinition accordingCell = ME.CellDefinitions[name];
				int nameLength = name.Length + 1;
				Expression returnExp = createExpression(expressionWithoutCause.Substring(
						nameLength, expressionWithoutCause.Length - 1 - nameLength), accordingCell);

				returnExp.CellDefinition = accordingCell;

				if (expressions.ContainsKey(name))
				{
					expressions[name].Add(returnExp);
				}
				else
				{
					expressions[name] = new List<Expression>();
					expressions[name].Add(returnExp);
				}

				returnExp.Causes = CreateCauses(cause);
				returnExp.ExpressionString = expression;
				return returnExp;
			}
			else
			{
				throw new Exception("Zelle mit dem Namen: " + name + " wurde nicht initialisiert, ändern sie den Namen, oder initialisieren Sie eine solche Zelle!");
			}

		}

		private List<CauseExpression> CreateCauses(string causesString)
		{
			List<CauseExpression> causeExpressions = new List<CauseExpression>();
			List<double> randomValues = new List<double>();

			string[] causes = causesString.Split('#');
			foreach (var causeString in causes)
			{
				string[] cause = causeString.Split(new string[] { "(", ")" }, StringSplitOptions.RemoveEmptyEntries);
				CellDefinition cd = ME.CellDefinitions[cause[0].Trim()];
				Dictionary<string, AttributExpression> attributeExpressions = new Dictionary<string, AttributExpression>();
				int index = 1;
				if (cause.Length > 1 && !cause[1].Trim().StartsWith(":"))
				{
					attributeExpressions = CreateAttributeExpressions(cd, cause[index].Replace(" ", ""));
					index++;
				}


				double value = ParseRandomValue(cause, index, causes.Length);

				randomValues.Add(value);
				causeExpressions.Add(new CauseExpression(cd, value, attributeExpressions));
			}
			double sum = 0;
			for (int i = 0; i < causeExpressions.Count; i++)
			{
				sum += causeExpressions[i].ChooseValue;
				causeExpressions[i].ChooseValue = sum;
			}
			if (!(sum > 0.9999999 && sum < 1.0000001))
			{
				throw new ArgumentException("Die Summe der Wahrscheinlichkeiten muss 1 ergeben");
			}

			return causeExpressions;
		}

		private double ParseRandomValue(string[] cause, int index, int causesLength)
		{
			if (cause.Length == index && causesLength != 1)
			{
				throw new ArgumentException("Es muss eine Wahrscheinlichkeit angegeben werden.");
			}
			return cause.Length == index ? 1 : double.Parse(cause[index].Substring(1));
		}

		private Dictionary<string, AttributExpression> CreateAttributeExpressions(CellDefinition cellDef, string expressionsString)
		{
			Dictionary<string, AttributExpression> attributes = new Dictionary<string, AttributExpression>();
			string[] expressions = expressionsString.Split(';');

			try
			{
				for (int i = 0; i < expressions.Length; i++)
				{
					string[] tmpExpr = expressions[i].Split(':');

					if (cellDef.Attributes.ContainsKey(tmpExpr[0]))
					{
						if (cellDef.Attributes[tmpExpr[0]] is StringAttribut)
						{
							attributes.Add(tmpExpr[0], new StringAttributExpression(tmpExpr[1]));
						}
						else
						{
							attributes.Add(tmpExpr[0], new NumberAttributExpression(tmpExpr[1], double.Parse(tmpExpr[2])));
						}
					}
				}
			}
			catch
			{
				throw new Exception("Die Attribute können so nicht verändert werden.");
			}
			return attributes;
		}

		private Expression createExpression(string stringExpression, CellDefinition accordingCell)
		{
			Token token = GetExpressionStructure(stringExpression);

			Expression expression = CreateExpressionFromToken(token);
			expression.CellDefinition = accordingCell;
			return expression;
		}

		private Expression CreateExpressionFromToken(Token token)
		{
			if (!token.LeftPart.HasChildren)
			{
				return new SingleExpression(token.LeftPart.ToString(), token.OperatorString, token.RightPart.ToString());
			}
			else
			{
				return new NestedExpression(CreateExpressionFromToken(token.LeftPart), token.OperatorString, CreateExpressionFromToken(token.RightPart));
			}

		}

		/// <summary>
		/// Sucht die Struktur des Ausdrucks und gibt sie als Baum von Tokens zurück
		/// </summary>
		/// <param name="stringExpression"></param>
		/// <returns></returns>
		private Token GetExpressionStructure(string stringExpression)
		{
			Token token = new Token();

			string pattern = @"( (and|or|xor) )|\<|\>|=";
			Regex regex;
			Match match;

			int klammerCounter = 0;
			bool searchingFirstKlammerAuf = true;
			int index = 0;

			for (int i = 0; i < stringExpression.Length; i++)
			{
				if (stringExpression[i] == '(')
				{
					klammerCounter++;
					// hier ist die erste Klammer auf
					if (klammerCounter == 1)
					{
						index = i + 1;
						searchingFirstKlammerAuf = false;
					}
				}

				if (stringExpression[i] == ')')
				{
					klammerCounter--;
				}

				if (klammerCounter == 0 && !searchingFirstKlammerAuf)
				{
					// Klammern um die komplette Expression
					if (index == 1 && i == stringExpression.Length - 1)
					{
						return GetExpressionStructure(stringExpression.Substring(1, stringExpression.Length - 2));
					}
					else if (index == 1)
					{
						regex = new Regex(pattern);
						match = regex.Match(stringExpression, i);
						return GetTokenFromMatch(stringExpression, match, index, i);
					}
					else
					{
						pattern += @"\s*$";
						regex = new Regex(pattern);
						match = regex.Match(stringExpression.Substring(0, i));
						return GetTokenFromMatch(stringExpression, match, index, i);
					}
				}
			}

			if (klammerCounter != 0)
				throw new Exception(
						"Ausgewertete Expression ist ungültig (Klammern): "
								+ stringExpression);

			pattern = @" (and|or|xor) ";

			regex = new Regex(pattern);
			match = regex.Match(stringExpression);
			if (!match.Success)
			{
				pattern = @"<|>|=";
				regex = new Regex(pattern);
				match = regex.Match(stringExpression);
			}
			return GetTokenFromMatch(stringExpression, match, 0, match.Index);
		}

		private Token GetTokenFromMatch(string stringExpression, Match match, int startIndex, int actualIndex)
		{
			Token token = new Token();
			if (match.Success)
			{
				string left = stringExpression.Substring(startIndex, actualIndex - startIndex).Trim();
				string right = stringExpression.Substring(match.Index + match.Length).Trim();
				if (match.Value == "<" || match.Value == ">" || match.Value == "=")
				{ //SingleExpression
					token.LeftPart = new Token() { OperatorString = left };
					token.OperatorString = match.Value;
					token.RightPart = new Token() { OperatorString = right };
				}
				else
				{ //NestedExpression
					token.LeftPart = GetExpressionStructure(left);
					token.OperatorString = match.Value.Trim();
					token.RightPart = GetExpressionStructure(right);
				}
				return token;
			}
			else
			{
				throw new ArgumentException("Die Regeln sind nicht wohlgeformt.");
			}
		}

		Random random = new Random();
		public void CalculateStep()
		{
			foreach (Cell xCell in ME.Cells)
			{
				if (xCell != null)
				{
					matchAgainstRules(xCell);
				}

			}

			int fieldLength = ME.Cells.GetLength(0) * ME.Cells.GetLength(1);
			foreach (Cell tmpCell in ME.Cells)
			{
				if (ME.Preferences.ActualizationType != ActualizationType.Sequential)
				{
					tmpCell.ChangeCellDefinition();
				}
				ME.Trajectory.AddToCurrentValue(tmpCell.CellDefinition);
			}
			ME.Trajectory.PrepareNextStep();
		}

		private void matchAgainstRules(Cell xCell)
		{
			if (expressions.ContainsKey(xCell.CellDefinition.Name))
			{
				foreach (Expression tmpEx in expressions[xCell.CellDefinition.Name])
				{
					bool eval = tmpEx.evaluate(xCell);

					if (eval)
					{
						xCell.SetCellDefinitionTarget(GetNexCellDefinition(tmpEx));
						if (ME.Preferences.ActualizationType == ActualizationType.Sequential)
						{
							xCell.ChangeCellDefinition();
						}
						return;
					}
				}
			}
		}

		private CauseExpression GetNexCellDefinition(Expression tmpEx)
		{
			double value = random.NextDouble();
			int count = 0;
			while (value >= tmpEx.Causes[count].ChooseValue && count < tmpEx.Causes.Count)
			{
				count++;
			}
			return tmpEx.Causes[count];
		}

	}
}
